/// \file fishmytest.cpp
/// \addtogroup fishmytest
/// @{
#include "fishmytest.h"

#ifndef DIM
#define DIM 3
#endif

#ifndef THREED
#define THREED
#endif

#include "fish/interface/ifish.h"
#include "fish/interface/ifisharguments.h"
#include "fish/interface/ifisharray.h"
#include "kernel/interface/iprogram.h"
#include "module/interface/itablelist.h"
#include "module/interface/itable.h"
#include "utility/interface/iarchive.h"
#include "shared/src/archive2.h"

#include <zone/interface/izone.h>
#include <zone/interface/izonelist.h>
#include <zone/interface/igp.h>
#include <zone/interface/igplist.h>

#include <Windows.h>

using namespace itasca;
using namespace zone;

#include <vector>
#include <tuple>
#include <string>
using std::vector;
using std::tuple;

//
// TecIO
//
#include <TECIO.h>

#ifndef CHECK_RES
#define CHECK_RES(res) assert((res) == 0)
#endif

#ifndef CHECK
#define CHECK(res) assert((res) == true)
#endif


enum VarLocation
{
	VarLocation_CellCentered = 0,
	VarLocation_Nodal = 1,
};

int FindGpId(const vector<int>& gpIds, const vector<const IGp*>& gpPointers, const IGp* pGp)
{
	int gpId = -1;
	auto itGpId = gpIds.cbegin();
	for (auto it = gpPointers.cbegin(); it != gpPointers.cend(); ++it)
	{
		if (*it == pGp)
		{
			gpId = *itGpId;
			break;
		}

		++itGpId;
	}
	CHECK(gpId > 0);
	return gpId;
}

/*
bool IsFileInUse(const QString& filePath)
{
	TCHAR* pBuf = new TCHAR[filePath.length() + 1];
	pBuf[filePath.length()] = L'\0';
	filePath.toWCharArray(pBuf);
	HANDLE fileHandle = ::CreateFile(
		pBuf,
		FILE_GENERIC_WRITE,
		FILE_SHARE_WRITE,
		nullptr,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		nullptr
	);
	if (fileHandle == INVALID_HANDLE_VALUE)
	{
		DWORD errorCode = GetLastError();
		if (errorCode == ERROR_SHARING_VIOLATION)
		{
			delete[] pBuf;
			return true;
		}
	}

	::CloseHandle(fileHandle);
	delete[] pBuf;
	return false;
}
*/

// version.txt is used with the provided version.rc to encode identification values in the final DLL.
// This includes version information and , and names.
// These are available by right-clicking the DLL as properties in Windows Explorer.
#include "version.txt"

/// Static data declaration.  
/// All copies of FishMytest will share this data.
uint FishMytest::version_ = 0;
double FishMytest::frequency_ = dPi;
double FishMytest::amplitude_ = 1.0;
double FishMytest::phase_ = 0.0;
double FishMytest::interval_ = 0.02;
qptrdiff FishMytest::number_ = 100;

/// The Itasca Plugin standard requires four functions be exported from the DLL.
/// These are getName(), getMajorVersion(), getMinorVersion(), and createInstance().
/// These are described below:

/// This function is required by all DLLs.  See Win32 documentation for details.
int __stdcall DllMain(void*, unsigned, void*)
{
	return 1;
}

/// Returns the name of the plugin, with a "fish" prefix.  
/// This is used to verify that the plugin represents the library you intended, in case the filename was changed.
extern "C" EXPORT_TAG const char* getName()
{
#ifdef FISHDEBUG  // Define FISHDEBUG if you want to have both the debug and release versions of the DLL loaded.
	// FISHDEBUG causes everything to be given a slightly different name, to differentiate them.
	// Normally, however, one would develop the DLL using debug settings and then compile with
	//  optimizations on when done.
	return "functionmytestd";
#else
	return "functionmytest";
#endif
}

/// Returns the major version number.  
/// This number indicates binary compatibility, and must match with the number expected by the FISH DLL.
extern "C" EXPORT_TAG unsigned getMajorVersion()
{
	return MAJOR_VERSION;
}

/// Returns a minor version number, used to indicate internal changes.
/// The use may specific a minimum minor version required during the load command.
extern "C" EXPORT_TAG unsigned getMinorVersion()
{
	return MINOR_VERSION;
}

/// This functions should return a single new instance of the library class.
/// The createIntrinsics() method is then called to register FISH intrinsics.
extern "C" EXPORT_TAG void* createInstance()
{
	return new FishMytest(0, "dummy", 0, "flt");
}

FishMytest::FishMytest(
	uint id,
	const QString& name,
	int numArguments,
	const QString& returnDescription,
	bool readOnly,
	const QString& argumentDescription
) : id_(id),
	name_(name),
	numArguments_(numArguments),
	returnDescription_(returnDescription),
	readOnly_(readOnly),
	argumentDescription_(argumentDescription)
{
}

QString FishMytest::getPluginName() const
{
#ifdef FISHDEBUG
	return "mytestd";
#else
	return "mytest";
#endif
}

QStringList FishMytest::createIntrinsics(IFish* fish)
{
#ifdef FISHDEBUG
	QString prefix = "mytest.debug.";
#else
	QString prefix = "mytest.";
#endif
	QStringList ret;
	fish->registerLibrary(new FishMytest(FreqFunction, prefix + "freq", 0, "flt", false));
	fish->registerLibrary(new FishMytest(AmpFunction, prefix + "amp", 0, "flt", false));
	fish->registerLibrary(new FishMytest(PhaseFunction, prefix + "phase", 0, "flt", false));
	fish->registerLibrary(new FishMytest(IntervalFunction, prefix + "int", 0, "flt", false));
	fish->registerLibrary(new FishMytest(NumberFunction, prefix + "number", 0, "int", false));

	fish->registerLibrary(new FishMytest(AllFunction, prefix + "all", -1, "int", true, "flt,?flt?,?flt?,?flt?,?int?"));
	fish->registerLibrary(new FishMytest(CreateFunction, prefix + "create", 1, "int", true, "int"));

	//
	fish->registerLibrary(new FishMytest(InputFunction, prefix + "input", 2, "flt", true, "array, int"));

	// TecIO
	fish->registerLibrary(new FishMytest(FileWriterOpenFunction, prefix + "FileWriterOpen", 2, "int", true, "fileName, varNames"));
	fish->registerLibrary(new FishMytest(FileWriterCloseFunction, prefix + "FileWriterClose", 1, "int", true, "fileHandle"));

	fish->registerLibrary(new FishMytest(ExportToTecplot, prefix + "ExportToTecplot", 1, "int", true, "filePath"));

	return ret;
}

void FishMytest::get(IProgram* prog, uint id, const IFishArguments* args, IFishParam* retValue)
{
	switch (id)
	{
	case FreqFunction:
		*retValue = frequency_;
		break; // Note that the operator=(const double &) method of the IFishParam interface is used here.
	case AmpFunction:
		*retValue = amplitude_;
		break;;
	case PhaseFunction:
		*retValue = phase_;
		break;
	case IntervalFunction:
		*retValue = interval_;
		break;
	case NumberFunction:
		*retValue = number_;
		break;
	case AllFunction:
		{
			// This function takes a variable number of arguments, so checking must be done manually and an exception thrown if incorrect.
			if (!args->getSize())
				throw std::runtime_error(build("Insufficient arguments to function %1", getName()).toStdString());
			frequency_ = args->get(0)->toDouble();
			// This is an example of how to get argument values, here the first argument is assumed to be a real.
			if (args->getSize() > 1)
				amplitude_ = args->get(1)->toDouble();
			if (args->getSize() > 2)
				phase_ = args->get(2)->toDouble();
			if (args->getSize() > 3)
				interval_ = args->get(3)->toDouble();
			if (args->getSize() > 4)
				number_ = args->get(4)->toInt(1);
			if (args->getSize() > 5)
				throw std::runtime_error(build("Too many arguments to function %1.", getName()).toStdString());
			*retValue = to<int>(0);
		}
		break;
	case CreateFunction:
		{
			// The mytest_create actually does the work, and is the first example of how to access the IProgram Interface.
			// First the ITableList interface is obtained from the IProgram interface.
			ITableList* tableList = prog->findInterface<ITableList>();
			if (!tableList)
				throw std::runtime_error(build("No table logic available in function %1.", getName()).toStdString());
			// Next an ITable is obtained given an integer ID number, passed as an argument.
			ITable* table = tableList->lookupOrCreateWithName(args->get(0)->toQString());
			if (!table)
				throw std::runtime_error(build("Error creating table in function %1.", getName()).toStdString());
			// The table is cleared, it case it was already in use previously.
			table->clear();
			// Values are added to the table to generate the sinusoid.
			for (int i = 0; i < number_; ++i)
			{
				double x = to<double>(i) * interval_;
				double y = amplitude_ * sin(frequency_ * x + phase_);
				table->add(DVect2(x, y));
			}
			// In this case the return value is meaningless, so by convention we set it to an integer of 0.
			// Alternatelly, we could have not thrown exceptions and instead returned error codes that must be checked by the caller in FISH.
			*retValue = to<int>(0);
		}
		break;
	case InputFunction:
		{
			/*
			IFishArray* pArray = args->get(0)->toPointer<IFishArray>();
			int count = pArray->getSize(0);
			double value = 0;
			for (int i=0;i<count;i++)
			{
				value += pArray->get(i)->toDouble();
			}

			*retValue = to<double>(value);
			**/
			IZoneList* pZoneList = prog->findInterface<IZoneList>();
			IGpList* pGpList = prog->findInterface<IGpList>();

			int cellCount = pZoneList->getNZone();
			int nodeCount = pGpList->getNGp();

			*retValue = to<double>(cellCount * 100 + nodeCount);
		}
		break;
	case FileWriterOpenFunction:
		{
			QString fileName = args->get(0)->toQString();
			QString extraVarNames = args->get(1)->toQString();

			QString varNames = "X Y Z xDisp yDisp zDisp sxx syy szz sxy syz sxz";
			varNames += " " + extraVarNames;

			QStringList varNameList = varNames.split(' ', QString::SkipEmptyParts);
			int varCount = varNameList.count();

			char dataSetTitle[] = "FLAC3D_DataSet";

			int fileFormat = FILEFORMAT_SZL;
			int fileType = FILETYPE_FULL;
			int defaultVarType = FieldDataType_Double;
			void* fileHandle = NULL;
			int res = ::tecFileWriterOpen(
				fileName.toLocal8Bit().data(),
				dataSetTitle,
				varNames.toLocal8Bit().data(),
				fileFormat,
				fileType,
				defaultVarType,
				NULL,
				&fileHandle
			);
			CHECK_RES(res);

			//
			//
			//
			IZoneList* pZoneList = prog->findInterface<IZoneList>();
			IGpList* pGpList = prog->findInterface<IGpList>();

			int cellCount = pZoneList->getNZone();
			int nodeCount = pGpList->getNGp();

			//
			// Node coordinate
			//
			vector<double> xCoords;
			vector<double> yCoords;
			vector<double> zCoords;

			//
			// Node displacement
			//
			vector<double> xDisps;
			vector<double> yDisps;
			vector<double> zDisps;

			//
			// Zone stress
			//
			vector<double> sxx;
			vector<double> syy;
			vector<double> szz;
			vector<double> sxy;
			vector<double> syz;
			vector<double> sxz;

			//
			// Iterate nodes
			//
			vector<int> gpIds;
			vector<const IGp*> gpPointers;
			{
				int gpId = 0;
				const IGp* pGp = pGpList->getFirstGp();
				while (pGp != 0)
				{
					++gpId;

					// Get node coordinate
					DVect pos = pGp->getPosition();
					xCoords.push_back(pos.x());
					yCoords.push_back(pos.y());
					zCoords.push_back(pos.z());

					// Get node displacement
					DVect disp = pGp->getDisplacement();
					xDisps.push_back(disp.x());
					yDisps.push_back(disp.y());
					zDisps.push_back(disp.z());

					//
					gpPointers.push_back(pGp);
					gpIds.push_back(gpId);

					//
					pGp = pGp->getNext();
				}
			}
			CHECK(gpIds.size() == nodeCount);

			//
			// Iterate zones
			//
			vector<int> cellNodeIds;
			const IZone* pZone = pZoneList->getFirstZone();
			while (pZone != 0)
			{
				vector<int> zoneGpIds;

				int gpCount = pZone->getNumGp();
				if (gpCount == 8)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));
					int p6 = FindGpId(gpIds, gpPointers, pZone->getGp(5));
					int p7 = FindGpId(gpIds, gpPointers, pZone->getGp(6));
					int p8 = FindGpId(gpIds, gpPointers, pZone->getGp(7));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p7);
					zoneGpIds.push_back(p8);
					zoneGpIds.push_back(p6);
				}
				else if (gpCount == 6)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));
					int p6 = FindGpId(gpIds, gpPointers, pZone->getGp(5));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p6);
					zoneGpIds.push_back(p6);
				}
				else if (gpCount == 5)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
				}
				else
				{
					CHECK(gpCount == 4);

					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
				}

				for (auto it = zoneGpIds.cbegin(); it != zoneGpIds.cend(); ++it)
				{
					cellNodeIds.push_back(*it);
				}

				// Get zone stress
				SymTensor stress = pZone->getAveStress();
				sxx.push_back(stress.s11());
				syy.push_back(stress.s22());
				szz.push_back(stress.s33());
				sxy.push_back(stress.s12());
				syz.push_back(stress.s23());
				sxz.push_back(stress.s13());

				//
				pZone = pZone->getNext();
			}
			CHECK(cellNodeIds.size() == cellCount * 8);

			//
			char zoneTitle[] = "_ZoneMesh";
			int zoneType = ZONETYPE_FEBRICK;

			vector<int> varTypes(varCount, FieldDataType_Double);
			int valLocations[] = {
				VarLocation_Nodal,
				VarLocation_Nodal,
				VarLocation_Nodal,

				VarLocation_Nodal,
				VarLocation_Nodal,
				VarLocation_Nodal,

				VarLocation_CellCentered,
				VarLocation_CellCentered,
				VarLocation_CellCentered,
				VarLocation_CellCentered,
				VarLocation_CellCentered,
				VarLocation_CellCentered,
			};
			//CHECK(valLocations.size() == varCount);

			//
			// Create zone
			//
			vector<int> passiveVarList(varCount, 0);
			vector<int> shareVarFromZone(varCount, 0);
			int shareConnectivityFromZone = 0;
			int numFaceConnections = 0;
			int faceNeighborMode = 0;
			int zoneIndex = 0;
			res = ::tecZoneCreateFE(
				fileHandle,
				zoneTitle,
				zoneType,
				nodeCount,
				cellCount,
				&varTypes[0],
				&shareVarFromZone[0],
				&valLocations[0],
				&passiveVarList[0],
				shareConnectivityFromZone,
				numFaceConnections,
				faceNeighborMode,
				&zoneIndex
			);
			CHECK_RES(res);

			//
			// Output variable data
			//
			vector<const vector<double>*> varDataVectors;
			varDataVectors.push_back(&xCoords);
			varDataVectors.push_back(&yCoords);
			varDataVectors.push_back(&zCoords);
			varDataVectors.push_back(&xDisps);
			varDataVectors.push_back(&yDisps);
			varDataVectors.push_back(&zDisps);

			varDataVectors.push_back(&sxx);
			varDataVectors.push_back(&syy);
			varDataVectors.push_back(&szz);
			varDataVectors.push_back(&sxy);
			varDataVectors.push_back(&syz);
			varDataVectors.push_back(&sxz);

			int varIndex = 0;
			for (auto it = varDataVectors.cbegin(); it != varDataVectors.cend(); ++it)
			{
				varIndex++;

				auto pDataVec = *it;
				res = ::tecZoneVarWriteDoubleValues(
					fileHandle,
					zoneIndex,
					varIndex,
					NULL,
					pDataVec->size(),
					&(*pDataVec)[0]
				);
				CHECK_RES(res);
			}

			//
			// Output element definition
			//
			const int nodeAreaOneBased = 1;
			res = ::tecZoneNodeMapWrite32(
				fileHandle,
				zoneIndex,
				NULL,
				nodeAreaOneBased,
				cellNodeIds.size(),
				&cellNodeIds[0]
			);
			CHECK_RES(res);

			//
			// Flush to file
			//
			res = ::tecFileWriterClose(&fileHandle);
			CHECK_RES(res);

			//*retValue = reinterpret_cast<int>(&fileHandle);
			*retValue = to<int>(res);
		}
		break;
	case FileWriterCloseFunction:
		{
			//void** pFileHandle = reinterpret_cast<void**>(args->get(0)->toInt());
			//int res = ::tecFileWriterClose(pFileHandle);
			//CHECK_RES(res);

			//*retValue = to<int>(res);
		}
		break;
	case ExportToTecplot:
		{
			//
			// Arguments
			//
			QString fileName = args->get(0)->toQString();

			//
			// Zone list and Node list pointer
			//
			IZoneList* pZoneList = prog->findInterface<IZoneList>();
			IGpList* pGpList = prog->findInterface<IGpList>();

			//
			// cell (element) count and node count
			//
			int cellCount = pZoneList->getNZone();
			int nodeCount = pGpList->getNGp();

			//
			// Output variable info
			//
			vector<tuple<QString, VarLocation, const vector<double>*>> varInfos;

			//
			// Get Node IDs
			//
			vector<int> gpIds;
			vector<const IGp*> gpPointers;
			int gpId = 0;
			const IGp* pGp = pGpList->getFirstGp();
			while (pGp != 0)
			{
				++gpId;

				// Get gridpoint pointer and ID
				gpPointers.push_back(pGp);
				gpIds.push_back(gpId);

				//
				pGp = pGp->getNext();
			}
			CHECK(gpIds.size() == nodeCount);

			//
			// Get Node coordinate
			//
			vector<double> xCoords;
			vector<double> yCoords;
			vector<double> zCoords;
			pGp = pGpList->getFirstGp();
			while (pGp != 0)
			{
				// Get node coordinate
				DVect pos = pGp->getPosition();
				xCoords.push_back(pos.x());
				yCoords.push_back(pos.y());
				zCoords.push_back(pos.z());

				//
				pGp = pGp->getNext();
			}
			varInfos.push_back(make_tuple(QString("X"), VarLocation_Nodal, &xCoords));
			varInfos.push_back(make_tuple(QString("Y"), VarLocation_Nodal, &yCoords));
			varInfos.push_back(make_tuple(QString("Z"), VarLocation_Nodal, &zCoords));

			//
			// Get Node displacement
			//
			vector<double> xDisps;
			vector<double> yDisps;
			vector<double> zDisps;
			pGp = pGpList->getFirstGp();
			while (pGp != 0)
			{
				// Get node displacement
				DVect disp = pGp->getDisplacement();
				xDisps.push_back(disp.x());
				yDisps.push_back(disp.y());
				zDisps.push_back(disp.z());

				//
				pGp = pGp->getNext();
			}
			varInfos.push_back(make_tuple(QString("xDisp"), VarLocation_Nodal, &xDisps));
			varInfos.push_back(make_tuple(QString("yDisp"), VarLocation_Nodal, &yDisps));
			varInfos.push_back(make_tuple(QString("zDisp"), VarLocation_Nodal, &zDisps));

			//
			// Get Zone stress
			//
			vector<double> sxx;
			vector<double> syy;
			vector<double> szz;
			vector<double> sxy;
			vector<double> syz;
			vector<double> sxz;
			const IZone* pZone = pZoneList->getFirstZone();
			while (pZone != 0)
			{
				// Get zone stress
				SymTensor stress = pZone->getAveStress();
				sxx.push_back(stress.s11());
				syy.push_back(stress.s22());
				szz.push_back(stress.s33());
				sxy.push_back(stress.s12());
				syz.push_back(stress.s23());
				sxz.push_back(stress.s13());

				//
				pZone = pZone->getNext();
			}
			varInfos.push_back(make_tuple(QString("sxx"), VarLocation_CellCentered, &sxx));
			varInfos.push_back(make_tuple(QString("syy"), VarLocation_CellCentered, &syy));
			varInfos.push_back(make_tuple(QString("szz"), VarLocation_CellCentered, &szz));
			varInfos.push_back(make_tuple(QString("sxy"), VarLocation_CellCentered, &sxy));
			varInfos.push_back(make_tuple(QString("syz"), VarLocation_CellCentered, &syz));
			varInfos.push_back(make_tuple(QString("sxz"), VarLocation_CellCentered, &sxz));

			//
			// Get Nodal connective list (Elem definition)
			//
			vector<int> cellNodeIds;
			pZone = pZoneList->getFirstZone();
			while (pZone != 0)
			{
				vector<int> zoneGpIds;

				int gpCount = pZone->getNumGp();
				if (gpCount == 8)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));
					int p6 = FindGpId(gpIds, gpPointers, pZone->getGp(5));
					int p7 = FindGpId(gpIds, gpPointers, pZone->getGp(6));
					int p8 = FindGpId(gpIds, gpPointers, pZone->getGp(7));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p7);
					zoneGpIds.push_back(p8);
					zoneGpIds.push_back(p6);
				}
				else if (gpCount == 6)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));
					int p6 = FindGpId(gpIds, gpPointers, pZone->getGp(5));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p6);
					zoneGpIds.push_back(p6);
				}
				else if (gpCount == 5)
				{
					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));
					int p5 = FindGpId(gpIds, gpPointers, pZone->getGp(4));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p5);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
				}
				else
				{
					CHECK(gpCount == 4);

					int p1 = FindGpId(gpIds, gpPointers, pZone->getGp(0));
					int p2 = FindGpId(gpIds, gpPointers, pZone->getGp(1));
					int p3 = FindGpId(gpIds, gpPointers, pZone->getGp(2));
					int p4 = FindGpId(gpIds, gpPointers, pZone->getGp(3));

					zoneGpIds.push_back(p1);
					zoneGpIds.push_back(p2);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p3);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
					zoneGpIds.push_back(p4);
				}

				for (auto it = zoneGpIds.cbegin(); it != zoneGpIds.cend(); ++it)
				{
					cellNodeIds.push_back(*it);
				}

				//
				pZone = pZone->getNext();
			}
			CHECK(cellNodeIds.size() == cellCount * 8);

			//
			// Variable name list and variable locations
			//
			QString varNames;
			vector<int> valLocations;
			for (auto it = varInfos.cbegin(); it != varInfos.cend(); ++it)
			{
				auto var_info = *it;
				auto varName = std::get<0>(var_info);
				auto varLocation = std::get<1>(var_info);

				varNames += varName + " ";
				valLocations.push_back(varLocation);
			}
			varNames = varNames.trimmed();

			//
			// Create szplt file
			//
			char dataSetTitle[] = "FLAC3D_DataSet";
			int fileFormat = FILEFORMAT_SZL;
			int fileType = FILETYPE_FULL;
			int defaultVarType = FieldDataType_Double;
			void* fileHandle = nullptr;
			int res = ::tecFileWriterOpen(
				fileName.toLocal8Bit().data(),
				dataSetTitle,
				varNames.toLocal8Bit().data(),
				fileFormat,
				fileType,
				defaultVarType,
				nullptr,
				&fileHandle
			);
			CHECK_RES(res);

			//
			// Create zone
			//
			int varCount = varInfos.size();

			char zoneTitle[] = "_ZoneMesh";
			int zoneType = ZONETYPE_FEBRICK;

			vector<int> varTypes(varCount, FieldDataType_Double);
			vector<int> passiveVarList(varCount, 0);
			vector<int> shareVarFromZone(varCount, 0);

			int shareConnectivityFromZone = 0;
			int numFaceConnections = 0;
			int faceNeighborMode = 0;

			int zoneIndex = 0;
			res = ::tecZoneCreateFE(
				fileHandle,
				zoneTitle,
				zoneType,
				nodeCount,
				cellCount,
				&varTypes[0],
				&shareVarFromZone[0],
				&valLocations[0],
				&passiveVarList[0],
				shareConnectivityFromZone,
				numFaceConnections,
				faceNeighborMode,
				&zoneIndex
			);
			CHECK_RES(res);

			//
			// Output variable data
			//
			for (int i = 0; i < varInfos.size(); ++i)
			{
				int varIndex = i + 1;
				auto pDataVec = std::get<2>(varInfos[i]);
				res = ::tecZoneVarWriteDoubleValues(
					fileHandle,
					zoneIndex,
					varIndex,
					0,
					pDataVec->size(),
					&(*pDataVec)[0]
				);
				CHECK_RES(res);
			}

			//
			// Output element definition
			//
			const int nodeAreaOneBased = 1;
			res = ::tecZoneNodeMapWrite32(
				fileHandle,
				zoneIndex,
				0,
				nodeAreaOneBased,
				cellNodeIds.size(),
				&cellNodeIds[0]
			);
			CHECK_RES(res);

			//
			// Flush and Close szplt file
			//
			res = ::tecFileWriterClose(&fileHandle);
			CHECK_RES(res);

			//
			*retValue = to<int>(res);
		}
		break;

	default: 
		throw std::runtime_error("Illegal ID in FishMytest::get().");
	}
}

void FishMytest::set(IProgram*, uint id, const IFishArguments*, const IFishParam* param)
{
	switch (id)
	{
	case FreqFunction:
		frequency_ = param->toDouble();
		break;
	case AmpFunction:
		amplitude_ = param->toDouble();
		break;
	case PhaseFunction:
		phase_ = param->toDouble();
		break;
	case IntervalFunction:
		interval_ = param->toDouble();
		break;
	case NumberFunction:
		number_ = param->toInt(1);
		break;
	default:
		throw std::runtime_error("Illegal ID in FishMytest::set().");
	}
}

void FishMytest::archiveLibrary(IArchive& a)
{
	if (getID())
		return; // only archive data if ID is 0, we only want one copy.

	QDataStream& ds = a.getDataStream(); // Get the QDataStream associated with the archive.
	a.version(version_, "FishMytest");
	// Store version information, in case the data changes and you want backwards compatibility.
	if (a.isSave())
	{
		// Save data to the stream if in save mode.
		ds << frequency_;
		ds << amplitude_;
		ds << phase_;
		ds << interval_;
		ds << number_;
	}
	else if (a.isRestore())
	{
		// Restore data from the stream if in restore mode.
		ds >> frequency_;
		ds >> amplitude_;
		ds >> phase_;
		ds >> interval_;
		ds >> number_;
	}
	// This particular mytest has no need of remap.
}

void FishMytest::saveLibrary(Archive2& a) const
{
	if (getID())
		return; // only save data if ID is 0, we only want one copy.

	a.save("Frequency", frequency_);
	a.save("Amplitude", amplitude_);
	a.save("Phase", phase_);
	a.save("Interval", interval_);
	a.save("Number", number_);
}

bool FishMytest::restoreLibrary(Archive2& a, quint64 label)
{
	if (label == runtime_encode("Frequency"))
		return a.restore(frequency_);
	if (label == runtime_encode("Amplitude"))
		return a.restore(amplitude_);
	if (label == runtime_encode("Phase"))
		return a.restore(phase_);
	if (label == runtime_encode("Interval"))
		return a.restore(interval_);
	if (label == runtime_encode("Number"))
		return a.restore(number_);

	return false;
}

void FishMytest::remapLibrary(Archive2&)
{
}

void FishMytest::reset()
{
	if (getID())
		return; // Only reset data if ID is 0, although resetting multiple times really harms nothing.

	frequency_ = dPi; // Reset data to default values.
	amplitude_ = 1.0;
	phase_ = 0.0;
	interval_ = 0.02;
	number_ = 100;
}

/// @}
// EoF
